##
# \file mapping.py
#

# --  Python imports  --
from math import floor, sqrt
# -- End Python imports --

# --  Pybrain imports  --
# -- End Pybrain imports --

# --  Project imports  --
from bjmath import BJMath
from namedcards import Cards
from hand import Hand
from players import Players
# -- End Project imports --

##
# \class Map
class Map:

    ## \var _PlayerOffset
    # Player offset value to create a 0 to N numbering.
    _PlayerOffset = (Cards.TWO + Cards.TWO)

    ## \var _DealerOffset
    # Dealer offset value to create a 0 to N numbering.
    _DealerOffset = (Cards.TWO)

    ## \var _MinEffectiveCount
    # For card counting, this is the smallest relevent count value.
    _MinEffectiveCount = 2

    ## \var _MaxEffectiveCount
    # For card counting, this is the largest relevent count value.
    _MaxEffectiveCount = 9

    _CountCards = False

    ##
    # \details Sets specific bits within a given integer.
    # \pre none
    # \post none
    # \param number The integer number that value will be inserted into at the given offset.
    # \param value The value to insert into number.
    # \param offset Bit offset within number.
    # \return The original number with the specified value at the given offset location.
    @staticmethod
    def setBits(number, value, offset):
        mask = value << offset
        return (number | mask)

    ##
    # \details Retrieves a section of bits within a given integer.
    # \pre none
    # \post none
    # \param number The integer number that contains the information for extraction.
    # \param length The total number of bits to extract.
    # \param offset Bit offset within number.
    # \return A number of bit count length extract from number starting at the offset location.
    @staticmethod
    def getBits(number, length, offset):
        mask   = (2 ** length) - 1
        result = number >> offset
        return (result & mask)

    @staticmethod
    def reduceCardCount(count):
        reduced_count = 0

        if count < Map._MinEffectiveCount:
            # If the count is less than two, the action is the same,
            # so map it to the same value.
            reduced_count = 0
        elif count >= Map._MaxEffectiveCount:
            # If the count is greater than ten, the action is the same,
            # so map it to the same value.
            reduced_count = (Map._MaxEffectiveCount - Map._MinEffectiveCount)
        else:
           # To give the rest of the values a 0 based numbering,
           # subtract two from the current count.
            reduced_count = count - Map._MinEffectiveCount

        return reduced_count
    ##
    # \details Creates to a unique numerical state from the current card counts.
    # \pre none
    # \post none
    # \param count Object of type CardCounter that contains the current card counts.
    # \return A number packed with all of the card counting techniques available.        
    @staticmethod
    def createRawCount(count):
        # Get all of the available card counting techniques. 0->8
        all_counts  = count.getAllCounts()

        # Bit length of reduced card counts
        length      = 3
        count_state = 0
        offset      = 0

        # For each count ...
        for count in all_counts:
            reduced_count = Map.reduceCardCount(count) 
            count_state   = Map.setBits(count_state, reduced_count, offset * length)
            offset = offset + 1

        return count_state

    ##
    # \details Creates a valid state from raw integer values.
    # \pre none
    # \post none
    # \param player_value The value of the players hand (see: )
    # \param delaer_value The value of the dealers hand (see: )
    # \param player_type The type of the players hand (see: )
    # \return Tuple of two numbers (k1, k2) that when Cantor paired produce the input parameter.
    @staticmethod
    def createRawState(player_value, dealer_value, player_type):
        state = 0
        state = Map.setBits(state, player_value, 0) # Length = 5
        state = Map.setBits(state, dealer_value, 5) # Length = 4
        state = Map.setBits(state, player_type,  9) # Length = 2

        return state

    ##
    # \details Maps the players hand, and the dealer up-card to a unique state.
    # \pre none
    # \post none
    # \param player_hand The players entire hand.
    # \param dealer_card The dealer up-card.
    # \return The unique state of the game.
    @staticmethod
    def createState(perseus):
        player_hand = perseus[Players.AGENT]
        dealer_card = perseus[Players.DEALER][0]
        card_counts = perseus[Players.CHEATER]

        # Input can be in the range:   4 to 22
        # Output must be in the range: 0 to N
        player_value = BJMath.calcHandValue(player_hand)

        # Subtract the smallest possible value to create a 0 to N numbering
        dealer_value = dealer_card - Cards.TWO

        # Map all "Busted" hands to a single state
        if player_value > Hand.BLACKJACK:
            player_value = Hand.BLACKJACK + 1

        # Subtract the smallest possible value to create a 0 to N numbering
        player_value = player_value - (Cards.TWO + Cards.TWO)

        player_type = BJMath.calcHandType(player_hand)

        state = Map.createRawState(player_value, dealer_value, player_type)

        # Card Counting
        if Map._CountCards == True:
            count = card_counts.KO_Count
            state = state << 3
            state = state | Map.reduceCardCount(count)

        return state

    ##
    # \details Separates a state into the values used to create the state.
    # \pre none
    # \post none
    # \param state The state to break apart.
    # \return Tuple of the players hand value and type, and the value of the dealers hand.
    @staticmethod
    def invertState(state):
        player_value = Map.getBits(state, 5, 0) # Length = 5
        dealer_value = Map.getBits(state, 4, 5) # Length = 4
        player_type  = Map.getBits(state, 2, 9) # Length = 2

        return (player_value, dealer_value, player_type)
